package demo.consistenthash;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

/**
 * 一致性hash算法的实现
 */
public class ConsistentHash<T> {

    //自定义hash方法
    private Hash<Object> hashMethod;

    //创建hash映射，虚拟节点映射真实节点
    private final Map<Integer, T> hashMap = new ConcurrentHashMap<>();

    //将所有的hash保存起来
    private List<Integer> keys = new ArrayList<>();

    //默认虚拟节点数量
    private final int replicas;

    public ConsistentHash() {
        this(3, Utils::rehash);
    }

    public ConsistentHash(int replicas, Hash<Object> hashMethod) {
        this.replicas = replicas;
        this.hashMethod = hashMethod;
    }

    @SafeVarargs
    public final void add(T... keys) {
        for (T key : keys) {
            //根据虚拟节点个数来计算虚拟节点
            for (int i = 0; i < this.replicas; i++) {
                //根据函数获取到对应的hash值
                int hash = this.hashMethod.hash(i + ":" + key.toString());
                this.keys.add(hash);
                this.hashMap.put(hash, key);
            }
        }
        //排序，因为是一个环状结构
        Collections.sort(this.keys);
    }

    /**
     * 根据对应的key来获取到节点信息
     *
     * @param key
     * @return
     */
    public T get(Object key) {
        Objects.requireNonNull(key, "key不能为空");
        int hash = this.hashMethod.hash(key);
        //获取到对应的节点信息
        int idx = Utils.search(this.keys.size(), h -> this.keys.get(h) >= hash);
        //如果idx == this.keys.size() ，就代表需要取 this.keys.get(0); 因为是环状，所以需要使用 % 来进行处理
        return this.hashMap.get(this.keys.get(idx % this.keys.size()));
    }

    @FunctionalInterface
    public static interface Hash<T> {
        /**
         * 计算hash值
         *
         * @param t
         * @return int类型
         */
        int hash(T t);
    }

    public static class Utils {

        static final int HASH_BITS = 0x7fffffff;

        /**
         * 通过二分查找法，定义数组索引位置
         *
         * @param len
         * @param f
         * @return
         */
        public static int search(int len, Function<Integer, Boolean> f) {
            int i = 0, j = len;
            //通过二分查找发来定为索引位置
            while (i < j) {
                //长度除于2
                int h = (i + j) >> 1;
                //调用函数，判断当前的索引值是否大于
                if (f.apply(h)) {
                    //向低半段进行遍历
                    j = h;
                } else {
                    //向高半段进行遍历
                    i = h + 1;
                }
            }
            return i;
        }

        /**
         * 将返回的hash能够平均的计算在 int类型之间
         *
         * @param o
         * @return
         */
        public static int rehash(Object o) {
            int h = o.hashCode();
            return (h ^ (h >>> 16)) & HASH_BITS;
        }
    }

    public static void main(String[] args) {
        ConsistentHash<String> consistentHash = new ConsistentHash<>();
        consistentHash.add("192.168.2.106", "192.168.2.107", "192.168.2.108");

        Map<String, Object> map = new HashMap<>();
        map.put("zs", "192.168.2.108");
        map.put("999999", "192.168.2.106");
        map.put("233333", "192.168.2.106");

        map.forEach((k, v) -> {
            String node = consistentHash.get(k);
            if (!v.equals(node)) {
                throw new IllegalArgumentException("节点获取错误，key：" + k + "，获取到的节点值为：" + node);
            }
        });

        consistentHash.add("192.168.2.109");
        map.put("zs", "192.168.2.109");
        map.forEach((k, v) -> {
            String node = consistentHash.get(k);
            if (!v.equals(node)) {
                throw new IllegalArgumentException("节点获取错误，key：" + k + "，获取到的节点值为：" + node);
            }
        });
    }

}
